#ifndef SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
#define SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH

#include "byte_stream.hh"

#include <algorithm>
#include <cstdint>
#include <list>
#include <optional>
#include <string>
#include <utility>

//! \brief A class that assembles a series of excerpts from a byte stream (possibly out of order,
//! possibly overlapping) into an in-order byte stream.
class StreamReassembler {
  private:
    ByteStream _output;                                   //!< The reassembled in-order byte stream
    const size_t _capacity;                               //!< The maximum number of bytes
    size_t _unassembled_num{0};                           // number of the unassembled bytes
    size_t _desired_idx{0};                               // the next desired index
    std::optional<size_t> _eof_idx{std::nullopt};         // off-the-end index, equivalent to last index + 1
    std::list<std::pair<size_t, std::string>> _buffer{};  // stored unassembled bytes and the index of the first byte

    // add data to the unassembled buffer
    void add_to_buffer(const std::pair<size_t, std::string> data);

    // check whether wanted_size will exceed the capacity
    // if so, return the max size it can add
    // otherwise, return the user's wanted size
    size_t check_capacity(const size_t &wanted_size) {
        auto left_size = _capacity - _unassembled_num - _output.buffer_size();
        return std::min(wanted_size, left_size);
    };

  public:
    //! \brief Construct a `StreamReassembler` that will store up to `capacity` bytes.
    //! \note This capacity limits both the bytes that have been reassembled,
    //! and those that have not yet been reassembled.
    StreamReassembler(const size_t capacity);

    //! \brief Receive a substring and write any newly contiguous bytes into the stream.
    //!
    //! The StreamReassembler will stay within the memory limits of the `capacity`.
    //! Bytes that would exceed the capacity are silently discarded.
    //!
    //! \param data the substring
    //! \param index indicates the index (place in sequence) of the first byte in `data`
    //! \param eof the last byte of `data` will be the last byte in the entire stream
    void push_substring(const std::string &data, const size_t index, const bool eof);

    //! \name Access the reassembled byte stream
    //!@{
    const ByteStream &stream_out() const { return _output; }
    ByteStream &stream_out() { return _output; }
    //!@}

    //! The number of bytes in the substrings stored but not yet reassembled
    //!
    //! \note If the byte at a particular index has been pushed more than once, it
    //! should only be counted once for the purpose of this function.
    size_t unassembled_bytes() const;

    //! \brief Is the internal state empty (other than the output stream)?
    //! \returns `true` if no substrings are waiting to be assembled
    bool empty() const;

    // the next stream index reassembler want
    size_t desired_index() const { return _desired_idx; }
};

#endif  // SPONGE_LIBSPONGE_STREAM_REASSEMBLER_HH
